<?php
class Mozg_Property_Params
{

	private $_data = array();
	private $_orig;
	private $_lastOrig;
	private $_activeObject;
	private $_defaultObject;

	private $_validator = false;
	private $_config;
	private $_forms = array();
	private $_dataCallback;

	protected $_pathId;

	/**
	 *
	 * __construct($config [, $params, $params ...])
	 */
	public function __construct(Mozg_Property_Config $config, $data = null, Mozg_Property_Params $activeObject = null, Mozg_Property_Params $defaultObject = null)
	{
		$this->_config = $config;
		$this->_pathId = str_replace('/', '_', $config->GetPath());

		if (null !== $data)
		{
			$this->AddData($data);
		}

		$this->_activeObject = $activeObject;
		$this->_defaultObject = $defaultObject;
	}

	public function Unique()
	{
		$id = (int)$this->GetId();
		return $this->_pathId.($id ? '_' .$id : '');
	}

	public function PrimaryKeyValue()
	{
		$key = $this->Config()->GetPrimaryKey();
		if (!$key) return null;

		return $this->{'Get' .$key}();
	}

	/*
	 * $callback - function callback, который будет вызываться в объекте для получения доп. данных
	 */
	public function DataCallback($callback)
	{
		$this->_dataCallback = $callback;
	}

	public function LoadFullData()
	{
	    if (!$this->_dataCallback) return $this;

	    $callback = $this->_dataCallback;
	    $this->_dataCallback = null;

	    //если данные не удалось установить, то возвращаем callback,
	    //чтобы при других уловиях можно было вызвать его повторно
	    $data = call_user_func_array($callback, array($this));
	    if ($data !== false)
	    {
	    	$this->AddData($data, true);
	    }
	    else
	    {
	    	$this->_dataCallback = $callback;
	    }

	    return $this;
	}

	public function Config()
	{
		return $this->_config;
	}

	protected function CorrectLang($fieldName, $lang = false)
	{
		if (!$this->_config->IsMultilangField($fieldName)) return false;

		if (!in_array($lang, Mozg_Lang::$langs))
		{
			$lang = Mozg_Lang::$active;
		}

		return $lang;
	}

    public function Get($fieldName, $lang = false)
    {
        $value = $this->_data[$fieldName];
        $lang = $this->CorrectLang($fieldName, $lang);
        if ($lang)
        {
        	$value = (is_array($value) && isset($value[$lang])) ? $value[$lang] : null;
        }

        if (is_null($value))
        {
        	if ($this->_activeObject)
        	{
				$value = $this->_activeObject->Get($fieldName, $lang);
        	}
        	else
        	{
	        	$value = $this->DefaultValue($fieldName, $lang);
        	}
        }

        return $value;
    }

	public function DefaultValue($fieldName, $lang = false)
	{
    	if (!$this->_defaultObject) return null;
		return $this->_defaultObject->{'Get' .$fieldName}($lang);
	}

	public function Set($fieldName, $value, $lang = false)
	{
		$value = $this->ConvertValue($fieldName, $value);

		$origValue = null;
		$isSet = $this->HasSet($fieldName, $lang);

		$lang = $this->CorrectLang($fieldName, $lang);
		if ($lang)
		{
			if ($isSet) $origValue = $this->_data[$fieldName][$lang];
			$this->_data[$fieldName][$lang] = $value;
		}
		else
		{
			if ($isSet) $origValue = $this->_data[$fieldName];
			$this->_data[$fieldName] = $value;
		}

		if (!is_null($origValue) && ($origValue != $value))
		{
			$this->OriginalObject(true)->Set($fieldName, $origValue, $lang);
		}

		return $this;
	}

	public function OriginalObject($create = false)
	{
		if ($create && !($this->_orig instanceof $this))
		{
			$className = get_class($this);
			$this->_orig = new $className($this->_config, null, $this, $this->_defaultObject);
			$this->ClearOrig();
		}

		return $this->_orig;
	}

	public function Orig()
	{
		if (!($this->_lastOrig instanceof $this))
		{
			$this->_lastOrig = $this->OriginalObject(true);
			while($this->_lastOrig->OriginalObject())
			{
				$this->_lastOrig = $this->_lastOrig->OriginalObject();
			}
		}

		return $this->_lastOrig;
	}

	public function ClearOrig()
	{
		$this->_lastOrig = null;
		if ($this->_activeObject)
		{
			$this->_activeObject->ClearOrig();
		}
	}

	protected function ConvertValue($fieldName, $value)
	{
		$type = $this->_config->GetFieldType($fieldName);

		switch($type)
		{
			case "bool":
			case "boolean":
				$value = (bool)$value;
				break;

			case "int":
			case "integer":
				$value = (int)$value;
				break;

			case "float":
			case "double":
			case "real":
				$value = (float)$value;
				break;

			case "string":
				$value = (string)$value;
				break;

			case "hash":
				if (is_string($value))
				{
					$value = Mozg::Lib('Json')->Decode($value, true);
				}
				else
				{
					$value = (array)$value;
				}
				break;

			case "array":
				if (is_string($value))
				{
					$value = trim($value, ",");
					if (strlen($value))
					{
						$value = explode(",", $value);
					}
					else
					{
						$value = array();
					}
				}
				else
				{
					$value = (array)$value;
				}
				break;

			case "object":
				$value = (object)$value;
				break;

			case "entity":
				$entityPath = $this->_config->GetEntityPath($fieldName);
				$entityConfig = Mozg_Property_Config::Get($entityPath);
				$entityClass = $entityConfig->GetEntityClass();
				if (!($value instanceof $entityClass))
				{
					$value = Mozg_Entity::Get($entityConfig, $value);
				}

				break;

			case "datetime":
		    	if (is_numeric($value))
		    	{
		    		$value = (int)$value;
		    	}
		    	else
		    	{
					$value = strtotime($value);
				}
				break;

			case "null":
				$value = null;
				break;

			default:
				//unchanged
		}

		return $value;
	}

    public function HasSet($fieldName, $lang = false)
    {
    	$is = isset($this->_data[$fieldName]);
    	$lang = $this->CorrectLang($fieldName, $lang);
		if ($is && $lang)
		{
			$is = isset($this->_data[$fieldName][$lang]);
		}

		return $is;
    }

	public function AddData($rawParams, $setOnlyMissed = false)
	{
		//prepare array data
		if (is_array($rawParams))
		{
			//ok
		}
		else if ($rawParams instanceof self)
		{
			$rawParams = $rawParams->ExportData();
		}
		else
		{
			$rawParams = $this->UnpackData($rawParams);
		}

		//$this->_data = $rawParams + $this->_data;

		//import data
		foreach ($rawParams as $key => $value)
    	{
    		if ($this->_config->IsMultilangField($key))
    		{
    			$value = (array)$value;
    		}
    		else
    		{
				$value = array("" => $value);
    		}

    		$methodName = 'Set' .trim($key);
    		if (method_exists($this, $methodName))
    		{
	    		foreach ($value as $lang => $val)
    			{
    				if($setOnlyMissed && $this->HasSet($key, $lang)) continue;

		    		$this->$methodName($val, $lang);
    			}
    		}
    		else
    		{
				foreach ($value as $lang => $val)
    			{
    				if($setOnlyMissed && $this->HasSet($key, $lang)) continue;

		    		$this->Set($key, $val, $lang);
    			}
    		}

    	}

		return $this;
	}

	public function ExportData()
	{
		return $this->_data;
	}

	public function ImportData($data)
	{
		$this->ClearData(array_keys($this->ExportData()));
		return $this->AddData($data);
	}

	public function Delete($fieldName, $lang = false)
	{
		if (!$this->HasSet($fieldName, $lang)) return $this;

		$origValue = null;

		$lang = $this->CorrectLang($fieldName, $lang);
		if ($lang)
		{
			$origValue = $this->_data[$fieldName][$lang];
			unset($this->_data[$fieldName][$lang]);
		}
		else
		{
			$origValue = $this->_data[$fieldName];
			unset($this->_data[$fieldName]);
		}

		if (!is_null($origValue))
		{
			$this->OriginalObject(true)->Set($fieldName, $origValue, $lang);
		}

		return $this;
	}

	public function ClearData($clearFields)
	{
		$clearFields = array_intersect($clearFields, array_keys($this->ExportData()));
		foreach ($clearFields as $fieldName)
		{
			if ($this->_config->IsMultilangField($fieldName))
			{
				$langs = Mozg_Lang::$langs;
			}
			else
			{
				$langs = array(false);
			}

			foreach ($langs as $lang)
			{
				$this->Delete($fieldName, $lang);
			}
		}

		return $this;
	}

	protected $_fieldsDelimiter = "\n";

	protected function UnpackData($rawParams)
	{
		$params = array();
		if (!$rawParams) return $params;

		$rows = explode($this->_fieldsDelimiter, trim($rawParams, "\0\n"));
		foreach ($rows as $row)
		{
			list($key, $packedValue) = explode('=', $row, 2);
			$key = trim($key);
			if (!strlen($key)) continue;

			$unpdVal = Mozg::Lib('Json')->Decode($packedValue);
			if (is_array($unpdVal))
			{
				$unpdKey = key($unpdVal);
				if (isset($params[$key]) && is_array($params[$key]) && array_key_exists($unpdKey, $params[$key]))
				{
					$params[$key][] = current($unpdVal);
				}
				else
				{
					$params[$key][$unpdKey] = current($unpdVal);
				}
			}
			else
			{
				$params[$key] = $unpdVal;
			}
		}

		return $params;
	}

	public function OriginalData()
	{
		$fields = $this->_config->GetFields();
		$data = array_intersect_key($this->ExportData(), array_flip($fields));
		$origData = array();
		foreach ($data as $key => $value)
		{
			if ($value instanceof self)
			{
				$origData[$key] = $value->OriginalData();
			}
			else
			{
				$origData[$key] = $value;
			}
		}

		return $origData;
	}

	protected function IsListArray($array)
	{
		return (is_array($array) && ($array == array_values($array)));
	}

	public function RawData($params)
	{
		$rawParams = array();

		foreach ($params as $key => $value)
		{
			if (is_array($value))
			{
				if ($this->IsListArray($value))
				{
					foreach ($value as $val)
					{
						$rawParams[] = $key.'=[' .Mozg::Lib('Json')->Encode($val, JSON_FORCE_UTF). ']';
					}
				}
				else
				{
					foreach ($value as $k => $val)
					{
						$rawParams[] = $key.'={' .Mozg::Lib('Json')->Encode(strval($k), JSON_FORCE_UTF). ':' .Mozg::Lib('Json')->Encode($val, JSON_FORCE_UTF). '}';
					}
				}
			}
			else
			{
				$rawParams[] = $key.'=' .Mozg::Lib('Json')->Encode($value, JSON_FORCE_UTF);
			}
		}

		return $this->_fieldsDelimiter.implode($this->_fieldsDelimiter, $rawParams).$this->_fieldsDelimiter;
	}

	public function __call($method, $args)
	{
		$meth = strtolower(substr($method, 0, 3));
		$name = $this->lName(substr($method, 3));

		//if (!$name) throw new Mozg_Exception_System('Invalid method called');

		switch ($meth)
		{
			case 'set':
				return $this->Set($name, $args[0], isset($args[1]) ? $args[1] : false);

			case 'get':
				return $this->Get($name, isset($args[0]) ? $args[0] : false);

			default:
				throw new Mozg_Exception_System(Mozg_Lang::_('UNKNOWN_METHOD ' .$meth));
		}
	}

	protected function lName($name)
	{
		$name{0} = strtolower($name{0});
		return $name;
	}

	public function Validator()
	{
		if (false !== $this->_validator) return $this->_validator;

		$validatorClass = $this->_config->GetValidatorClass();
		$this->_validator = new $validatorClass($this);

		return $this->_validator;
	}

	public function Form($formPath)
	{
		if (!($this->_forms[$formPath] instanceof Mozg_Form_Entity))
		{
			$this->_forms[$formPath] = Mozg_Form::Get($formPath, $this);
		}

		return $this->_forms[$formPath];
	}

	public function __toString()
	{
		return $this->RawData($this->OriginalData());
	}
}